// Copyright (c) 2023 Cesanta Software Limited
// All rights reserved

#include <stddef.h>
#include "net.h"
#include "board_ex.h"

#define NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
#define JSON_HEADERS "Content-Type: application/json\r\n" NO_CACHE_HEADERS

struct mg_mgr g_mgr;

struct attribute
{
  const char *name;
  const char *type;
  const char *format;
  size_t offset;
  size_t size;
  bool readonly;
};

struct apihandler
{
  const char *name;
  const char *type;
  bool readonly;
  int read_level;
  int write_level;
  unsigned long version;
};

struct apihandler_custom
{
  struct apihandler common;
  void (*reply)(struct mg_connection *, struct mg_http_message *);
};

struct apihandler_action
{
  struct apihandler common;
  bool (*checker)(void); // Checker function for actions
  void (*starter)(void); // Starter function for actions
};

struct apihandler_data
{
  struct apihandler common;
  const struct attribute *attributes; // Points to the strucure descriptor
  size_t data_size;                   // Size of C structure
  void (*getter)(void *);             // Getter/check/begin function
  void (*setter)(void *);             // Setter/start/end function
};

struct apihandler_array
{
  struct apihandler common;
  const struct attribute *attributes; // Points to the strucure descriptor
  size_t data_size;                   // Size of C structure
  void (*getter)(uint64_t, void *);   // Getter/check/begin function
  void (*setter)(uint64_t, void *);   // Setter/start/end function
  uint64_t (*sizer)(void);            // Array size, for data handlers only
};

struct apihandler_reboot
{
  struct apihandler common;
  bool (*reboot)(void);
};

struct action_state
{
  char marker;      // Tells that we're an action connection
  bool (*fn)(void); // Action status function
};

struct attribute s_device_info_attributes[] =
{
    {"name", "string", NULL, offsetof(struct device_info, device_name), 0, false},
    {"chipId", "string", NULL, offsetof(struct device_info, uid), 0, false},
    {"firmwareVersion", "int", NULL, offsetof(struct device_info, firmware_version), 0, false},
    {"uptime", "int", NULL, offsetof(struct device_info, up_time), 0, false},
    {"cpuUsage", "double", "%.1f", offsetof(struct device_info, cpu_usage), 0, false},
    {"memoryUsage", "double", "%.1f", offsetof(struct device_info, memory_usage), 0, false},
    {"temperature", "double", "%.1f", offsetof(struct device_info, temperature), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_led_attributes[] =
{
    {"number", "int", NULL, offsetof(struct led, number), 0, false},
    {"state", "bool", NULL, offsetof(struct led, state), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_buzzer_attributes[] =
{
    {"frequency", "int", NULL, offsetof(struct buzzer, frequency), 0, false},
    {"state", "bool", NULL, offsetof(struct buzzer, state), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_ds18b20_attributes[] =
{
    {"temperature", "double", "%.1f", offsetof(struct ds18b20, temperature), 0, false},
    {"accuracy", "double", "%.3f", offsetof(struct ds18b20, accuracy), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};

struct attribute s_ecat_status_attributes[] =
{
    {"slaveALStatus", "string", NULL, offsetof(struct ecat_status, slave_state), 0, false},
    {"syncMode", "string", NULL, offsetof(struct ecat_status, sync_mode), 0, false},
    {"syncPeriod", "double", NULL, offsetof(struct ecat_status, sync_period_us), 0, false},
    {"jitter", "int", NULL, offsetof(struct ecat_status, sync_jitter_ns), 0, false},
    {"digitalInputs", "int", NULL, offsetof(struct ecat_status, digital_inputs), 0, false},
    {"digitalOutputs", "int", NULL, offsetof(struct ecat_status, digital_outputs), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};

struct attribute s_ecat_input_attributes[] =
{
    {"index", "int", NULL, offsetof(struct ecat_input, index), 0, false},
    {"state", "int", NULL, offsetof(struct ecat_input, state), 0, false},
    {NULL, NULL, NULL, 0, 0, false}
};

struct apihandler_data s_apihandler_device_info = {{"heartbeat", "data", true, 3, 3, 0UL}, s_device_info_attributes, sizeof(struct device_info), (void (*)(void *))board_get_device_info, NULL};
struct apihandler_reboot s_apihandler_reboot = {{"reboot", "reboot", false, 3, 3, 0UL}, (bool (*)(void))board_reboot};
struct apihandler_data s_apihandler_led = {{"led", "data", false, 3, 3, 0UL}, s_led_attributes, sizeof(struct led), (void (*)(void *))board_get_led, (void (*)(void *))board_set_led};
struct apihandler_data s_apihandler_buzzer = {{"buzzer", "data", false, 3, 3, 0UL}, s_buzzer_attributes, sizeof(struct buzzer), (void (*)(void *))board_get_buzzer, (void (*)(void *))board_set_buzzer};
struct apihandler_data s_apihandler_db18b20 = {{"temperature", "data", true, 3, 3, 0UL}, s_ds18b20_attributes, sizeof(struct ds18b20), (void (*)(void *))board_get_ds18b20, NULL};
struct apihandler_data s_apihandler_ecat_status = {{"ethercat/data", "data", true, 3, 3, 0UL}, s_ecat_status_attributes, sizeof(struct ecat_status), (void (*)(void *))board_get_ecat_status, NULL};
struct apihandler_data s_apihandler_ecat_input = {{"ethercat/input", "data", true, 3, 3, 0UL}, s_ecat_input_attributes, sizeof(struct ecat_input), (void (*)(void *))board_get_ecat_input, (void (*)(void *))board_set_ecat_input};

static struct apihandler *s_apihandlers[] = {
    (struct apihandler *)&s_apihandler_device_info,
    (struct apihandler *)&s_apihandler_reboot,
    (struct apihandler *)&s_apihandler_led,
    (struct apihandler *)&s_apihandler_buzzer,
    (struct apihandler *)&s_apihandler_db18b20,
    (struct apihandler *)&s_apihandler_ecat_status,
    (struct apihandler *)&s_apihandler_ecat_input,
};

void mg_json_get_str2(struct mg_str json, const char *path, char *buf, size_t len)
{
  struct mg_str s = mg_json_get_tok(json, path);
  if (s.len > 1 && s.buf[0] == '"')
  {
    mg_json_unescape(mg_str_n(s.buf + 1, s.len - 2), buf, len);
  }
}

static void handle_action(struct mg_connection *c, struct mg_http_message *hm, bool (*check_fn)(void), void (*start_fn)(void))
{
  if (hm->body.len > 0)
  {
    start_fn();
    if (check_fn())
    {
      struct action_state *as = (struct action_state *)c->data;
      as->marker = 'A';
      as->fn = check_fn;
    }
    else
    {
      mg_http_reply(c, 200, JSON_HEADERS, "false");
    }
  }
  else
  {
    mg_http_reply(c, 200, JSON_HEADERS, "%s", check_fn() ? "true" : "false");
  }
}

size_t print_struct(void (*out)(char, void *), void *ptr, va_list *ap)
{
  const struct attribute *a = va_arg(*ap, struct attribute *);
  char *data = va_arg(*ap, char *);
  size_t i, len = 0;
  for (i = 0; a[i].name != NULL; i++)
  {
    char *attrptr = data + a[i].offset;
    len += mg_xprintf(out, ptr, "%s%m:", i == 0 ? "" : ",", MG_ESC(a[i].name));
    if (strcmp(a[i].type, "int") == 0)
    {
      len += mg_xprintf(out, ptr, "%d", *(int *)attrptr);
    }
    else if (strcmp(a[i].type, "long long") == 0)
    {
      len += mg_xprintf(out, ptr, "%lld", *(long long *)attrptr);
    }
    else if (strcmp(a[i].type, "double") == 0)
    {
      const char *fmt = a[i].format;
      if (fmt == NULL)
        fmt = "%g";
      len += mg_xprintf(out, ptr, fmt, *(double *)attrptr);
    }
    else if (strcmp(a[i].type, "bool") == 0)
    {
      len += mg_xprintf(out, ptr, "%s", *(bool *)attrptr ? "true" : "false");
    }
    else if (strcmp(a[i].type, "string") == 0)
    {
      len += mg_xprintf(out, ptr, "%m", MG_ESC(attrptr));
    }
    else
    {
      len += mg_xprintf(out, ptr, "null");
    }
  }
  return len;
}

static void handle_object(struct mg_connection *c, struct mg_http_message *hm, struct apihandler_data *h)
{
  void *data = calloc(1, h->data_size);
  h->getter(data);
  if (hm->body.len > 0 && h->data_size > 0)
  {
    char *tmp = calloc(1, h->data_size);
    size_t i;
    memcpy(tmp, data, h->data_size);
    for (i = 0; h->attributes[i].name != NULL; i++)
    {
      const struct attribute *a = &h->attributes[i];
      char jpath[100];
      mg_snprintf(jpath, sizeof(jpath), "$.%s", a->name);
      if (strcmp(a->type, "int") == 0)
      {
        double d;
        if (mg_json_get_num(hm->body, jpath, &d))
        {
          int v = (int)d;
          memcpy(tmp + a->offset, &v, sizeof(v));
        }
      }
      else if (strcmp(a->type, "bool") == 0)
      {
        mg_json_get_bool(hm->body, jpath, (bool *)(tmp + a->offset));
      }
      else if (strcmp(a->type, "double") == 0)
      {
        mg_json_get_num(hm->body, jpath, (double *)(tmp + a->offset));
      }
      else if (strcmp(a->type, "string") == 0)
      {
        mg_json_get_str2(hm->body, jpath, tmp + a->offset, a->size);
      }
    }

    h->setter(tmp);
    free(tmp);
    h->getter(data); // Re-sync again after setting
  }
  mg_http_reply(c, 200, JSON_HEADERS, "{%M}\n", print_struct, h->attributes, data);
  free(data);
}

static size_t print_array(void (*out)(char, void *), void *ptr, va_list *ap)
{
  struct apihandler_array *ha = va_arg(*ap, struct apihandler_array *);
  uint64_t size = *va_arg(*ap, uint64_t *);
  uint64_t start = *va_arg(*ap, uint64_t *);
  size_t i, max = 20, len = 0;
  void *data = calloc(1, ha->data_size);
  for (i = 0; i < max && start + i < size; i++)
  {
    ha->getter(start + i, data);
    if (i > 0)
      len += mg_xprintf(out, ptr, ",");
    len += mg_xprintf(out, ptr, "{%M}", print_struct, ha->attributes, data);
  }
  free(data);
  return len;
}

static void handle_array(struct mg_connection *c, struct mg_http_message *hm, struct apihandler_array *h)
{
  char buf[40] = "";
  uint64_t size = h->sizer();
  uint64_t start = 0;
  mg_http_get_var(&hm->query, "start", buf, sizeof(buf));
  if (!mg_str_to_num(mg_str(buf), 10, &start, sizeof(start)))
    start = 0;
  mg_http_reply(c, 200, JSON_HEADERS, "{%m:%llu, %m:%llu, %m:[%M]}\n",
                MG_ESC("size"), size, MG_ESC("start"), start, MG_ESC("data"),
                print_array, h, &size, &start);
}

static struct apihandler *get_api_handler(struct mg_str name)
{
  size_t num_handlers = sizeof(s_apihandlers) / sizeof(s_apihandlers[0]);
  size_t i;
  if (name.len == 0)
    return NULL;
  if (num_handlers == 0)
    return NULL;
  for (i = 0; i < num_handlers; i++)
  {
    struct apihandler *h = s_apihandlers[i];
    size_t n = strlen(h->name);
    if (n > name.len)
      continue;
    if (strncmp(name.buf, h->name, n) != 0)
      continue;
    if (name.len > n && name.buf[n] != '/')
      continue;
    return h;
  }
  return NULL;
}

static struct apihandler *find_handler(struct mg_http_message *hm)
{
  if (hm->uri.len < 6 || strncmp(hm->uri.buf, "/api/", 5) != 0)
    return NULL;
  return get_api_handler(mg_str_n(hm->uri.buf + 5, hm->uri.len - 5));
}

static void handle_api_call(struct mg_connection *c, struct mg_http_message *hm, struct apihandler *h)
{
  if (strcmp(h->type, "object") == 0 || strcmp(h->type, "data") == 0)
  {
    handle_object(c, hm, (struct apihandler_data *)h);
  }
  else if (strcmp(h->type, "array") == 0)
  {
    handle_array(c, hm, (struct apihandler_array *)h);
  }
  else if (strcmp(h->type, "action") == 0)
  {
    struct apihandler_action *ha = (struct apihandler_action *)h;
    handle_action(c, hm, ha->checker, ha->starter);
  }
  else if (strcmp(h->type, "reboot") == 0)
  {
      mg_http_reply(c, 200, JSON_HEADERS, "reboot");
      ((struct apihandler_reboot *)h)->reboot();
  }
  else
  {
    mg_http_reply(c, 500, JSON_HEADERS, "API type %s unknown\n", h->type);
  }
}

// SNTP timer function. Sync up time
static void timer_sntp_fn(void *param) {
  mg_sntp_connect(param, "udp://time.google.com:123", NULL, NULL);
}

// HTTP request handler function
static void fn(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_ACCEPT) {
    if (c->fn_data != NULL) {  // TLS listener!
      struct mg_tls_opts opts = {0};
      opts.cert = mg_unpacked("/certs/server_cert.pem");
      opts.key = mg_unpacked("/certs/server_key.pem");
      mg_tls_init(c, &opts);
    }
  } else if (ev == MG_EV_HTTP_MSG) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    struct apihandler *h = find_handler(hm);
    if (h != NULL) {
      handle_api_call(c, hm, h);
    } else {
      struct mg_http_serve_opts opts;
      memset(&opts, 0, sizeof(opts));
      opts.root_dir = "/web_root";  // On embedded, use packed files
      opts.fs = &mg_fs_packed;
      mg_http_serve_dir(c, ev_data, &opts);
    }
    MG_DEBUG(("%lu %.*s %.*s", c->id, (int) hm->method.len, hm->method.buf,
              (int) hm->uri.len, hm->uri.buf));
  }
}

// web init
void web_init(struct mg_mgr *mgr) {

  mg_http_listen(mgr, HTTP_URL, fn, NULL);
  mg_http_listen(mgr, HTTPS_URL, fn, (void *) 1);
  mg_timer_add(mgr, 10 * 60 * 1000, MG_TIMER_RUN_NOW | MG_TIMER_REPEAT,
               timer_sntp_fn, mgr);
}

